Function Calls

supports three major calling conventions. The convention used depends on the amount of information available at compile time:

Local
Local call is used when the call and the called function are compiled at the same time. Using the term "convention" to describe this call mechanism is somewhat of a misnomer, since the compiler can do whatever it wants.

Named
Named call is used when the call is to a global function whose name is known at compile time.

Anonymous
Anonymous call is used when the function called is unknown until run time.

#| IR2 function call:

Environment manipulation code is always emitted at the location of the Bind or Return node for a Lambda.

Implicit args to functions in IR2: old-cont: cont to restore on return return-pc: pc to return to env: pointer to current closure (if heap) closure<n>: closed values for current closure (if stack)

Other info needed for IR2 conversion of functions: base pointers for all heap closures consed by this function also have passing locs for each explicit arg return strategy (known or unknown) and return locs

All arguments including implicit ones must have both a passing TN and a permanent TN. Passing locs for let calls can be the actual TN that holds the variable in the case of local variables. Set closure variables must still have a separate passing TN.

If we know the values counts for the argument continuations, then we compile local mv-calls by moving the TNs for the values continuations into the argument passing locations. Other mv-calls must be compiled using various hairy stack-hacking VOPs and unknown argument count call VOPs.

For now, we will create the callee's frame just before the call, instead of creating it before the evaluation of the first argument. If we created the environment early, then we would be able to move the argument values directly into the frame, instead of having to store them somewhere else for a while. The problem with early creation is that lifetime analysis gets confused because there is more than one instance of the same TN present simultaneously in the case where there are nested calls to the same function.

It turns out that there isn't a problem with a simple self-call, because the TN in the called frame is really the "same" TN as the one in the current frame, due to the restricted way in which we use the passing TNs.

We emit code for external entry points during IR2 conversion. The external entry point is the place where we start running in a full call from a function-entry. It does arg count checking and dispatching, moves the arguments into the passing locations for the for the lambda being called, and calls the lambda, moving the results into the standard locations if there aren't there already. |#

In IR2, the environment manipulation semantics of function call are decoupled from the control semantics. When allocating closure variables for a Let, it is possible to do environment manipulation with only the normal sequential control flow. In the case of a Let call with the same environment, we neither manipulate the environment nor transfer control; we merely initialize the variables with Move VOPs.

If a local function returns a known number of values which is less than the number expected by the caller, then additional code must be inserted at the return site which sets the unused values to NIL.

The full function call mechanism must effectively be a subset of the local call mechanism, since the two mechanisms must mesh at entry points and full function calls. A full call turns into some kind of full call VOP. There are different VOPs for calling named functions and closures. We also have tail-recursive full call VOPs. Arguments are set up using Move VOPs, just as for local call. The only difference is that the passing locations and conventions are restricted to the standard ones.

The gory details of arg count checking and dispatching are buried in the Function-Entry VOP, which takes a functional and a list of continuations, one pointing to each external entry.



Subsections